#include "StdAfx.h"

#if USE_CRYLOBBY_GAMESPY

#include "CryGameSpyLobby.h"
#include "CryGameSpyFriends.h"

#define MAX_FRIENDS_TASK_TIME								(10000)
#define GAMESPY_INVALID_BUDDY_INDEX					(-1)

#define FRIENDS_PARAM_GET_FRIENDS_LIST_NUM	(0)
#define FRIENDS_PARAM_CURRENT_GPPROFILE			(1)
#define FRIENDS_PARAM_GPPROFILES						(0)
#define FRIENDS_PARAM_NUM_GPPROFILES				(0)

CCryGameSpyFriends::CCryGameSpyFriends(CCryLobby* pLobby, CCryLobbyService* pService)
: CCryFriends(pLobby, pService)
, m_pGPConnection(NULL)
, m_GPProfile(0)
{
	// Make the CCryFriends base pointers point to our data so we can use the common code in CCryFriends
	for (uint32 index = 0; index < MAX_FRIENDS_TASKS; ++index)
	{
		CCryFriends::m_pTask[index] = &m_task[index];
	}
}

void CCryGameSpyFriends::Tick(CTimeValue tv)
{
	for (uint32 i = 0; i < MAX_FRIENDS_TASKS; i++)
	{
		STask* pTask = &m_task[i];

		if (pTask->used && pTask->running)
		{
			if ( pTask->m_haveGPConnection && !pTask->m_taskStarted )
			{
				pTask->m_taskStarted = true;
				pTask->m_timeStarted = g_time;

				switch (pTask->startedTask)
				{
				case eT_FriendsGetFriendsList:
					StartFriendsGetFriendsList(i);
					break;

				case eT_FriendsSendGameInvite:
					StartFriendsSendGameInvite(i);
					break;

				case eT_FriendsGetNamesList:
					StartFriendsGetNamesList(i);
					break;
				}
			}

			if ( pTask->m_taskStarted && pTask->running && (pTask->error == eCLE_Success) )
			{
				switch (pTask->subTask)
				{
				case eT_FriendsGetFriendsList:
					TickFriendsGetFriendsList(i);
					break;

				case eT_FriendsSendGameInvite:
					TickFriendsSendGameInvite(i);
					break;

				case eT_FriendsGetNamesList:
					TickFriendsGetNamesList(i);
					break;
				}
			}

			if ( pTask->error != eCLE_Success )
			{
				StopTaskRunning( i );
			}
		}
	}

	ProcessActiveFriendStateChanges();
}

void CCryGameSpyFriends::ProcessActiveFriendStateChanges() 
{
	while ( !m_activeProfileQueue.empty() )
	{
		GPProfile					profile = m_activeProfileQueue.front();
		SGameSpyProfile&	buddy = m_ProfileMap[ profile ];

		m_activeProfileQueue.pop();

		if ( ( buddy.m_buddyBits & eBB_TestShouldSendForwardAcceptMask ) == eBB_ShouldSendForwardAccept )
		{
			if ( gpAuthBuddyRequest( m_pGPConnection, profile ) == GP_NO_ERROR )
			{
				buddy.m_buddyBits |= ( eBB_SentForwardAccept | eBB_IsReverseBuddy );
			}
			else
			{
				buddy.m_buddyBits &= ~( eBB_SetupOriginateMask | eBB_SetupAnswerMask );
			}
		}

		if ( ( buddy.m_buddyBits & eBB_RevokeAnswerMask ) == eBB_ReceivedForwardRevoke )
		{
			if ( gpRevokeBuddyAuthorization( m_pGPConnection, profile ) == GP_NO_ERROR )
			{
				buddy.m_buddyBits |= eBB_SentReverseRevoke;
				buddy.m_buddyBits &= ~eBB_IsReverseBuddy;
			}
			else
			{
				buddy.m_buddyBits &= ~( eBB_RevokeOriginateMask | eBB_RevokeAnswerMask );
			}
		}

		// Clean up completed states.

		if ( ( buddy.m_buddyBits & eBB_SetupOriginateMask ) == eBB_SetupOriginateMask )
		{
			buddy.m_buddyBits &= ~eBB_SetupOriginateMask;
		}

		if ( ( buddy.m_buddyBits & eBB_SetupAnswerMask ) == eBB_SetupAnswerMask )
		{
			buddy.m_buddyBits &= ~eBB_SetupAnswerMask;
		}

		if ( ( buddy.m_buddyBits & eBB_RevokeOriginateMask ) == eBB_RevokeOriginateMask )
		{
			buddy.m_buddyBits &= ~eBB_RevokeOriginateMask;
		}

		if ( ( buddy.m_buddyBits & eBB_RevokeAnswerMask ) == eBB_RevokeAnswerMask )
		{
			buddy.m_buddyBits &= ~eBB_RevokeAnswerMask;
		}

		buddy.m_buddyBits &= ~eBB_InActiveQueue;
	}
}

ECryLobbyError CCryGameSpyFriends::FriendsGetFriendsList(uint32 user, uint32 start, uint32 num, CryLobbyTaskID* pTaskID, CryFriendsGetFriendsListCallback pCb, void* pCbArg)
{
	LOBBY_AUTO_LOCK;

	ECryLobbyError error = eCLE_Success;
	CryFriendsTaskID fTaskID = CryFriendsInvalidTaskID;

	error = StartTask(eT_FriendsGetFriendsList, false, user, &fTaskID, pTaskID, CrySessionInvalidHandle, pCb, pCbArg);

	if (error == eCLE_Success)
	{
		STask* pTask = &m_task[fTaskID];

		pTask->paramsNum[FRIENDS_PARAM_GET_FRIENDS_LIST_NUM] = num;

		FROM_GAME_TO_LOBBY(&CCryGameSpyFriends::StartTaskRunning, this, fTaskID);
	}

	return error;
}

ECryLobbyError CCryGameSpyFriends::FriendsGetNamesList(uint32 user, CryUserID* pUserIDs, uint32 numUserIDs, CryLobbyTaskID* pTaskID, CryFriendsGetFriendsListCallback pCb, void* pCbArg)
{
	LOBBY_AUTO_LOCK;

	ECryLobbyError error = eCLE_Success;
	CryFriendsTaskID fTaskID = CryFriendsInvalidTaskID;

	if (numUserIDs > 0)
	{
		error = StartTask(eT_FriendsGetNamesList, false, user, &fTaskID, pTaskID, CrySessionInvalidHandle, pCb, pCbArg);

		if (error == eCLE_Success)
		{
			STask* pTask = &m_task[fTaskID];
			pTask->paramsNum[FRIENDS_PARAM_NUM_GPPROFILES] = numUserIDs;

			error = CreateTaskParamMem(fTaskID, FRIENDS_PARAM_GPPROFILES, NULL, numUserIDs*sizeof(GPProfile));

			if (error == eCLE_Success)
			{
				GPProfile* pGPProfiles = (GPProfile*)m_pLobby->MemGetPtr(pTask->paramsMem[FRIENDS_PARAM_GPPROFILES]);

				for (uint32 index = 0; index < numUserIDs; index++)
				{
					if (pUserIDs[index].IsValid())
					{
						pGPProfiles[index] = ((SCryGameSpyUserID*)pUserIDs[index].get())->m_profileID;
					}
					else
					{
						error = eCLE_InvalidUser;
						break;
					}
				}
			}

			if (error == eCLE_Success)
			{
				FROM_GAME_TO_LOBBY(&CCryGameSpyFriends::StartTaskRunning, this, fTaskID);
			}
			else
			{
				FreeTask(fTaskID);
			}
		}
	}
	else
	{
		error = eCLE_InvalidRequest;
	}

	NetLog("[Lobby] CCryGameSpyFriends::FriendsGetNamesList() result %d", error);
	return error;
}

ECryLobbyError CCryGameSpyFriends::FriendsSendGameInvite(uint32 user, CrySessionHandle h, CryUserID* pUserIDs, uint32 numUserIDs, CryLobbyTaskID* pTaskID, CryFriendsCallback pCb, void* pCbArg)
{
	LOBBY_AUTO_LOCK;

	ECryLobbyError error = eCLE_Success;
	CCryMatchMaking* pMatchMaking = (CCryMatchMaking*)m_pLobby->GetMatchMaking();

	if (pMatchMaking)
	{
		CryFriendsTaskID fTaskID = CryFriendsInvalidTaskID;

		h = pMatchMaking->GetSessionHandleFromGameSessionHandle(h);

		error = StartTask(eT_FriendsSendGameInvite, false, user, &fTaskID, pTaskID, h, pCb, pCbArg);
		if (error == eCLE_Success)
		{
			STask* pTask = &m_task[fTaskID];
			pTask->paramsNum[FRIENDS_PARAM_NUM_GPPROFILES] = numUserIDs;

			if (numUserIDs > 0)
			{
				error = CreateTaskParamMem(fTaskID, FRIENDS_PARAM_GPPROFILES, NULL, numUserIDs*sizeof(GPProfile));

				if (error == eCLE_Success)
				{
					GPProfile* pGPProfiles = (GPProfile*)m_pLobby->MemGetPtr(pTask->paramsMem[FRIENDS_PARAM_GPPROFILES]);

					for (uint32 index = 0; index < numUserIDs; index++)
					{
						if (pUserIDs[index].IsValid())
						{
							pGPProfiles[index] = ((SCryGameSpyUserID*)pUserIDs[index].get())->m_profileID;
						}
						else
						{
							error = eCLE_InvalidUser;
							break;
						}
					}
				}
			}
			else
			{
				error = eCLE_InvalidUser;
			}

			if (error == eCLE_Success)
			{
				FROM_GAME_TO_LOBBY(&CCryGameSpyFriends::StartTaskRunning, this, fTaskID);
			}
			else
			{
				FreeTask(fTaskID);
			}
		}
	}
	else
	{
		error = eCLE_ServiceNotSupported;
	}

	NetLog("[Lobby] CCryGameSpyFriends::FriendsSendGameInvite() for game, result %d", error);
	return error;
}

void CCryGameSpyFriends::InitialiseCallbacks(GPConnection* pGPConnection)
{
	m_pGPConnection = pGPConnection;

	GPResult result = gpSetCallback(m_pGPConnection, GP_RECV_BUDDY_REQUEST, GPRecvBuddyRequestCallback, this);
	CRY_ASSERT_MESSAGE(result == GP_NO_ERROR, "Unable to register GP_RECV_BUDDY_REQUEST callback");

	if (result == GP_NO_ERROR)
	{
		result = gpSetCallback(m_pGPConnection, GP_RECV_BUDDY_STATUS, GPRecvBuddyStatusCallback, this);
		CRY_ASSERT_MESSAGE(result == GP_NO_ERROR, "Unable to register GP_RECV_BUDDY_STATUS callback");
	}

	if (result == GP_NO_ERROR)
	{
		result = gpSetCallback(m_pGPConnection, GP_RECV_BUDDY_MESSAGE, GPRecvBuddyMessageCallback, this);
		CRY_ASSERT_MESSAGE(result == GP_NO_ERROR, "Unable to register GP_RECV_BUDDY_MESSAGE callback");
	}

	if (result == GP_NO_ERROR)
	{
		result = gpSetCallback(m_pGPConnection, GP_RECV_GAME_INVITE, GPRecvGameInviteCallback, this);
		CRY_ASSERT_MESSAGE(result == GP_NO_ERROR, "Unable to register GP_RECV_GAME_INVITE callback");
	}

	if (result == GP_NO_ERROR)
	{
		result = gpSetCallback(m_pGPConnection, GP_RECV_BUDDY_AUTH, GPRecvBuddyAuthCallback, this);
		CRY_ASSERT_MESSAGE(result == GP_NO_ERROR, "Unable to register GP_RECV_BUDDY_AUTH callback");
	}

	if (result == GP_NO_ERROR)
	{
		result = gpSetCallback(m_pGPConnection, GP_RECV_BUDDY_REVOKE, GPRecvBuddyRevokeCallback, this);
		CRY_ASSERT_MESSAGE(result == GP_NO_ERROR, "Unable to register GP_RECV_BUDDY_REVOKE callback");
	}

	if (result != GP_NO_ERROR)
	{
		NetLog("[Lobby] : CCryGameSpyFriends::InitialiseCallbacks() unable to set GameSpy gp callbacks");
	}
}

void CCryGameSpyFriends::LateInitialise(GPProfile gpProfile)
{
	GPResult			result;

	m_GPProfile = gpProfile;

	for ( TProfileMapIterator it = m_ProfileMap.begin(); it != m_ProfileMap.end(); ++it )
	{
		it->second.m_buddyBits = 0;
		it->second.m_message.clear();
		it->second.m_utmMessage.clear();
		it->second.m_invite.clear();
	}

	result = gpGetReverseBuddies( m_pGPConnection, GP_NON_BLOCKING, GPGetReverseBuddyListCallback, this );

	if ( result != GP_NO_ERROR )
	{
		NetLog("[Lobby] : CCryGameSpyFriends::InitialiseCallbacks() unable to set GameSpy gp callbacks");
	}
}

uint64 CCryGameSpyFriends::GetInviteSessionID(GPConnection* pGPConnection, GPProfile gpProfile)
{
	uint64 sid = 0;

	TProfileMapIterator it = m_ProfileMap.find(gpProfile);
	if (it != m_ProfileMap.end())
	{
		SConfigurationParams neededInfo[1] = { CLCC_GAMESPY_PRODUCTID, {NULL} };
		m_pLobby->GetConfigurationInformation(neededInfo, 1);
		uint32 productID = neededInfo[0].m_32;

		const char* pDecode = it->second.m_invite.c_str();
		uint64 inviteProductID = atol(pDecode);

		if (productID == inviteProductID)
		{
			pDecode = strpbrk(pDecode, " ");
			if (pDecode != NULL)
			{
				sscanf_s( pDecode, "%llX", &sid );
			}

			pDecode = strpbrk(pDecode + 1, " ");
			if (pDecode != NULL)
			{
				ICVar* pPasswordCVar = gEnv->pConsole->GetCVar("sv_password");
				if (pPasswordCVar != NULL)
				{
					pPasswordCVar->Set(pDecode + 1);
					NetLog("[Lobby] CCryGameSpyFriends::GetInviteSessionID() received invite to session %llx, password '%s'", sid, pPasswordCVar->GetString());
				}
			}
		}
	}

	return sid;
}

ECryLobbyError CCryGameSpyFriends::StartTask(ETask etask, bool startRunning, uint32 user, CryFriendsTaskID* pFTaskID, CryLobbyTaskID* pLTaskID, CrySessionHandle h, void* pCb, void* pCbArg)
{
	CryFriendsTaskID tmpFTaskID;
	CryFriendsTaskID* pUseFTaskID = pFTaskID ? pFTaskID : &tmpFTaskID;
	ECryLobbyError error = CCryFriends::StartTask(etask, startRunning, pUseFTaskID, pLTaskID, h, pCb, pCbArg);

	if (error == eCLE_Success)
	{
		STask* pTask = &m_task[*pUseFTaskID];

		pTask->m_haveGPConnection = GetService()->HaveGPConnection();
		pTask->m_taskStarted = false;		// Used to hold the task until the various external gamespy services are ready (for tasks that require this)
	}

	return error;
}

void CCryGameSpyFriends::StartTaskRunning(CryFriendsTaskID fTaskID)
{
	LOBBY_AUTO_LOCK;

	STask* pTask = &m_task[fTaskID];

	if (pTask->used)
	{
		pTask->running = true;

		switch (pTask->startedTask)
		{
		case eT_FriendsGetFriendsList: // intentional fall-through
		case eT_FriendsSendGameInvite: // intentional fall-through
		case eT_FriendsGetNamesList:
			GetService()->SetNeedGPConnection(true);
			break;
		}
	}
}

void CCryGameSpyFriends::EndTask(CryFriendsTaskID fTaskID)
{
	LOBBY_AUTO_LOCK;

	STask* pTask = &m_task[fTaskID];

	if (pTask->used)
	{
		if (pTask->pCb)
		{
			switch (pTask->startedTask)
			{
			case eT_FriendsGetFriendsList:
				EndFriendsGetFriendsList(fTaskID);
				break;

			case eT_FriendsSendGameInvite:
				((CryFriendsCallback)pTask->pCb)(pTask->lTaskID, pTask->error, pTask->pCbArg);
				break;

			case eT_FriendsGetNamesList:
				EndFriendsGetNamesList(fTaskID);
				break;
			}
		}

		if (pTask->error != eCLE_Success)
		{
			NetLog("[Lobby] CCryGameSpyFriends::EndTask() %d (%d) Result %d", pTask->startedTask, pTask->subTask, pTask->error);
		}

		FreeTask(fTaskID);
	}
}

void CCryGameSpyFriends::StopTaskRunning(CryFriendsTaskID fTaskID)
{
	STask* pTask = &m_task[fTaskID];

	if (pTask->used)
	{
		pTask->running = false;

		TO_GAME_FROM_LOBBY(&CCryGameSpyFriends::EndTask, this, fTaskID);
	}
}

void CCryGameSpyFriends::StartFriendsGetFriendsList(CryFriendsTaskID fTaskID)
{
	STask* pTask = &m_task[fTaskID];

	// Look for the first profile that hasn't got a name
	TProfileMapIterator it = m_ProfileMap.begin();
	while ((it != m_ProfileMap.end()) && (it->second.m_buddyBits & eBB_IsBuddy) && (it->second.m_nickname.length() != 0))
	{
		++it;
	}

	if (it != m_ProfileMap.end())
	{
		pTask->paramsNum[FRIENDS_PARAM_CURRENT_GPPROFILE] = m_GPProfile;
		gpGetInfo(m_pGPConnection, it->first, GP_CHECK_CACHE, GP_NON_BLOCKING, GPGetInfoCallback, this);
		UpdateTaskError(fTaskID, CCryGameSpyLobbyService::GetErrorFromGameSpy(gpGetInfo(m_pGPConnection, it->first, GP_CHECK_CACHE, GP_NON_BLOCKING, GPGetInfoCallback, this), m_pGPConnection));
	}
	else
	{
		// Fully populated - early out
		StopTaskRunning(fTaskID);
	}

	if (pTask->error != eCLE_Success)
	{
		StopTaskRunning(fTaskID);
	}
}

void CCryGameSpyFriends::TickFriendsGetFriendsList(CryFriendsTaskID fTaskID)
{
	STask* pTask = &m_task[fTaskID];

	if (pTask->used)
	{
		uint64 timeTaken = (g_time - pTask->m_timeStarted).GetMilliSecondsAsInt64();

		if (timeTaken <= MAX_FRIENDS_TASK_TIME)
		{
			// Look for the first profile that hasn't got a name
			TProfileMapIterator it = m_ProfileMap.begin();
			while ((it != m_ProfileMap.end()) && ( it->second.m_buddyBits & eBB_IsBuddy ) && (it->second.m_nickname.length() != 0))
			{
				++it;
			}

			if (it == m_ProfileMap.end())
			{
				// All done
				StopTaskRunning(fTaskID);
			}
		}
		else
		{
			StopTaskRunning(fTaskID);
		}
	}
}

void CCryGameSpyFriends::EndFriendsGetFriendsList(CryFriendsTaskID fTaskID)
{
	STask* pTask = &m_task[fTaskID];

	SFriendInfo* pFriends = NULL;
	if (m_ProfileMap.size() > 0)
	{
		uint32 friendCount = 0;

		for (TProfileMapIterator it = m_ProfileMap.begin(); it != m_ProfileMap.end(); ++it)
		{
			if ( it->second.m_buddyBits & eBB_IsBuddy )
			{
				++friendCount;
			}
		}

		if (friendCount > 0)
		{
			pFriends = new SFriendInfo[friendCount];
			if(pFriends)
			{
				memset(pFriends, 0, friendCount * sizeof(SFriendInfo));
			}
			else
			{
				UpdateTaskError(fTaskID, eCLE_OutOfMemory);
			}
		}
	}

	uint32 index = 0;
	if (pTask->error == eCLE_Success)
	{
		for (TProfileMapIterator it = m_ProfileMap.begin(); (it != m_ProfileMap.end()) && (pTask->error == eCLE_Success); ++it)
		{
			if ( it->second.m_buddyBits & eBB_IsBuddy )
			{
				pFriends[index].userID = new SCryGameSpyUserID(it->first);
				strcpy(pFriends[index].name, it->second.m_nickname.c_str());
				if (pFriends[index].userID == NULL)
				{
					UpdateTaskError(fTaskID, eCLE_OutOfMemory);
					break;
				}

				++index;
			}
		}
	}

	if (pTask->error == eCLE_Success)
	{
		((CryFriendsGetFriendsListCallback)pTask->pCb)(pTask->lTaskID, pTask->error, pFriends, index, pTask->pCbArg);
	}
	else
	{
		((CryFriendsGetFriendsListCallback)pTask->pCb)(pTask->lTaskID, pTask->error, NULL, 0, pTask->pCbArg);
	}

	delete[] pFriends;
}

void CCryGameSpyFriends::StartFriendsGetNamesList(CryFriendsTaskID fTaskID)
{
	STask* pTask = &m_task[fTaskID];
	GPProfile* pGPProfiles = (GPProfile*)m_pLobby->MemGetPtr(pTask->paramsMem[FRIENDS_PARAM_GPPROFILES]);
	uint32 numUserIDs = pTask->paramsNum[FRIENDS_PARAM_NUM_GPPROFILES];

	GPProfile profileID = 0;
	uint32		profileLastUpdateTimestamp = 0;
	bool			doNameLookup = false;

	for (uint32 index = 0; index < numUserIDs; ++index)
	{
		SGameSpyProfile profile;
		TProfileMapIterator it = m_ProfileMap.find(pGPProfiles[index]);
		if (it == m_ProfileMap.end())
		{
			// Don't have a record of this profile, so add it
			std::pair<TProfileMapIterator, bool> result = m_ProfileMap.insert(TProfileMapElement(pGPProfiles[index], profile));

			if (result.second == true)
			{
				profileID = pGPProfiles[index];
				profileLastUpdateTimestamp = profile.m_lastUpdateTimestamp;
				doNameLookup = true;
			}
		}
		else
		{
			// Have a record of this profile - do we have a name?
			if (it->second.m_nickname.length() == 0)
			{
				profileID = it->first;
				profileLastUpdateTimestamp = it->second.m_lastUpdateTimestamp;
				doNameLookup = true;
			}
		}
	}

	// All the profiles have now been added to the map
	if (doNameLookup)
	{
		// Start populating names
		pTask->m_timeStarted = g_time;
		pTask->paramsNum[FRIENDS_PARAM_CURRENT_GPPROFILE] = profileID;
		UpdateTaskError(fTaskID, CCryGameSpyLobbyService::GetErrorFromGameSpy(gpGetInfo(m_pGPConnection, profileID, GP_CHECK_CACHE, GP_NON_BLOCKING, GPGetInfoCallback, this), this->m_pGPConnection));
	}
	else
	{
		// Fully populated - early out
		StopTaskRunning(fTaskID);
	}
}

void CCryGameSpyFriends::TickFriendsGetNamesList(CryFriendsTaskID fTaskID)
{
	STask* pTask = &m_task[fTaskID];
	GPProfile* pGPProfiles = (GPProfile*)m_pLobby->MemGetPtr(pTask->paramsMem[FRIENDS_PARAM_GPPROFILES]);
	uint32 numUserIDs = pTask->paramsNum[FRIENDS_PARAM_NUM_GPPROFILES];

	if (pTask->used)
	{
		uint64 timeTaken = (g_time - pTask->m_timeStarted).GetMilliSecondsAsInt64();

		if (timeTaken <= MAX_FRIENDS_TASK_TIME)
		{
			bool done = true;
			
			for (uint32 index = 0; index < numUserIDs; ++index)
			{
				SGameSpyProfile& profile = m_ProfileMap[pGPProfiles[index]];
				if (profile.m_nickname.length() == 0)
				{
					done = false;
				}
			}

			if (done)
			{
				StopTaskRunning(fTaskID);
			}
		}
		else
		{
			StopTaskRunning(fTaskID);
		}
	}
}

void CCryGameSpyFriends::EndFriendsGetNamesList(CryFriendsTaskID fTaskID)
{
	STask* pTask = &m_task[fTaskID];
	GPProfile* pGPProfiles = (GPProfile*)m_pLobby->MemGetPtr(pTask->paramsMem[FRIENDS_PARAM_GPPROFILES]);
	uint32 numUserIDs = pTask->paramsNum[FRIENDS_PARAM_NUM_GPPROFILES];

	SFriendInfo* pFriends = new SFriendInfo[numUserIDs];
	memset(pFriends, 0, numUserIDs * sizeof(SFriendInfo));

	if (pFriends != NULL)
	{
		for (uint32 index = 0; index < numUserIDs; ++index)
		{
			SGameSpyProfile& profile = m_ProfileMap[pGPProfiles[index]];
			pFriends[index].userID = new SCryGameSpyUserID(pGPProfiles[index]);
			strcpy(pFriends[index].name, profile.m_nickname.c_str());

			if (pFriends[index].userID == NULL)
			{
				pTask->error = eCLE_OutOfMemory;
			}
		}
	}
	else
	{
		pTask->error = eCLE_OutOfMemory;
	}

	if (pTask->error == eCLE_Success)
	{
		((CryFriendsGetFriendsListCallback)pTask->pCb)(pTask->lTaskID, pTask->error, pFriends, numUserIDs, pTask->pCbArg);
	}
	else
	{
		((CryFriendsGetFriendsListCallback)pTask->pCb)(pTask->lTaskID, pTask->error, NULL, 0, pTask->pCbArg);
	}

	delete[] pFriends;
}

void CCryGameSpyFriends::StartFriendsSendGameInvite(CryFriendsTaskID fTaskID)
{
	CryFixedStringT<GP_LOCATION_STRING_LEN> inviteString;
	STask* pTask = &m_task[fTaskID];
	GPProfile* pGPProfiles = (GPProfile*)m_pLobby->MemGetPtr(pTask->paramsMem[FRIENDS_PARAM_GPPROFILES]);
	uint32 numUserIDs = pTask->paramsNum[FRIENDS_PARAM_NUM_GPPROFILES];

	SConfigurationParams neededInfo[1] = { CLCC_GAMESPY_PRODUCTID, {NULL} };
	m_pLobby->GetConfigurationInformation(neededInfo, 1);
	uint32 productID = neededInfo[0].m_32;

	ICVar* pPasswordCVar = gEnv->pConsole->GetCVar("sv_password");
	const char* pPassword = (pPasswordCVar != NULL) ? pPasswordCVar->GetString() : "";

	CCryGameSpyMatchMaking* pMatchMaking = static_cast<CCryGameSpyMatchMaking*>(m_pLobby->GetMatchMaking());
	inviteString.Format("%d %llX %s", productID, pMatchMaking->GetSIDFromSessionHandle(pTask->session), pPassword);

	for (uint32 index = 0; index < numUserIDs; ++index)
	{
		UpdateTaskError(fTaskID, CCryGameSpyLobbyService::GetErrorFromGameSpy(gpInvitePlayer(m_pGPConnection, pGPProfiles[index], productID, inviteString.c_str()), m_pGPConnection));
		NetLog("[Lobby] CCryGameSpyFriends::StartFriendsSendGameInvite() Sent invite to profile %i, error %i, password '%s'", pGPProfiles[index], pTask->error, pPassword);
	}
}

void CCryGameSpyFriends::TickFriendsSendGameInvite(CryFriendsTaskID fTaskID)
{
	StopTaskRunning(fTaskID);
}

CryFriendsTaskID CCryGameSpyFriends::GetTaskID(GPConnection* pGPConnection, GPProfile gpProfile)
{
	STask* pTask = NULL;
	CryFriendsTaskID fTaskID = 0;
	for (fTaskID = 0; fTaskID < MAX_FRIENDS_TASKS; ++fTaskID)
	{
		pTask = &m_task[fTaskID];

		if ((m_pGPConnection == pGPConnection) && (pTask->paramsNum[FRIENDS_PARAM_CURRENT_GPPROFILE] == gpProfile))
		{
			break;
		}
	}

	return (fTaskID < MAX_FRIENDS_TASKS) ? fTaskID : CryFriendsInvalidTaskID;
}

void CCryGameSpyFriends::DispatchProfileOnlineStatus(GPProfile user, EOnlineState state)
{
	UCryLobbyEventData data;
	SCryLobbyFriendOnlineStateData stateData;
	data.pFriendOnlineStateData = &stateData;

	stateData.m_user = new SCryGameSpyUserID(user);
	stateData.m_curState = state;

	if (stateData.m_user.IsValid())
	{
		m_pLobby->DispatchEvent(eCLSE_FriendOnlineState, data);
	}
}

void CCryGameSpyFriends::UpdateProfile(GPConnection* pGPConnection, GPProfile profile, uint32 timestamp)
{
	int buddyIndex;
	GPResult result = gpGetBuddyIndex(pGPConnection, profile, &buddyIndex);
	if ((result == GP_NO_ERROR) && (buddyIndex != GAMESPY_INVALID_BUDDY_INDEX))
	{
		GPRecvBuddyStatusArg buddyStatusArg;
		buddyStatusArg.profile = profile;
		buddyStatusArg.date = timestamp;
		buddyStatusArg.index = buddyIndex;
		GPRecvBuddyStatusCallback(pGPConnection, &buddyStatusArg, this);
	}
	else
	{
		SGameSpyProfile& buddy = m_ProfileMap[profile];
		if (buddy.m_nickname.length() == 0)
		{
			// This is not a buddy (yet?) so just get the name for this profile
			gpGetInfo(pGPConnection, profile, GP_CHECK_CACHE, GP_NON_BLOCKING, GPGetInfoCallback, this);
		}
	}
}

void CCryGameSpyFriends::GPRecvBuddyRequestCallback(GPConnection* pGPConnection, void* pArg, void* pParam)
{
	CCryGameSpyFriends* _this = static_cast<CCryGameSpyFriends*>(pParam);
	if (pGPConnection == _this->m_pGPConnection)
	{
		GPRecvBuddyRequestArg* pBuddyRequestArg = static_cast<GPRecvBuddyRequestArg*>(pArg);

		SGameSpyProfile& buddy = _this->m_ProfileMap[pBuddyRequestArg->profile];
		if (buddy.m_lastUpdateTimestamp <= pBuddyRequestArg->date)
		{
			if ( CNetCVars::Get().gamespySymmetricalFriends )
			{
				if ( buddy.m_buddyBits & eBB_SentForwardRequest )
				{
					buddy.m_buddyBits |= eBB_ReceivedReverseRequest;

					if ( !( buddy.m_buddyBits & eBB_InActiveQueue ) )
					{
						buddy.m_buddyBits |= eBB_InActiveQueue;
						_this->m_activeProfileQueue.push( pBuddyRequestArg->profile );
					}
				}
				else
				{
					buddy.m_buddyBits |= eBB_ReceivedForwardRequest;
					TO_GAME_FROM_LOBBY( &CCryGameSpyFriends::DispatchFriendIDEvent, _this, eCLSE_FriendRequest, pBuddyRequestArg->profile );
				}
			}
			else
			{
				TO_GAME_FROM_LOBBY( &CCryGameSpyFriends::DispatchFriendIDEvent, _this, eCLSE_FriendRequest, pBuddyRequestArg->profile );
			}

			_this->UpdateProfile(pGPConnection, pBuddyRequestArg->profile, pBuddyRequestArg->date);
		}
	}
}

void CCryGameSpyFriends::GPRecvBuddyStatusCallback(GPConnection* pGPConnection, void* pArg, void* pParam)
{
	CCryGameSpyFriends* _this = static_cast<CCryGameSpyFriends*>(pParam);
	if (pGPConnection == _this->m_pGPConnection)
	{
		GPRecvBuddyStatusArg* pBuddyStatusArg = static_cast<GPRecvBuddyStatusArg*>(pArg);

		SGameSpyProfile& buddy = _this->m_ProfileMap[pBuddyStatusArg->profile];
		if (buddy.m_lastUpdateTimestamp <= pBuddyStatusArg->date)
		{
			GPBuddyStatus buddyStatus;
			GPResult result = gpGetBuddyStatus(_this->m_pGPConnection, pBuddyStatusArg->index, &buddyStatus);
			if (result == GP_NO_ERROR)
			{
				const char*		pFriendsOldSessionURL = NULL;
				const char*		pFriendsNewSessionURL = NULL;
				bool					wasBuddy = ( ( buddy.m_buddyBits & eBB_IsBuddy ) != 0 );
				bool					isBuddy = ( gpIsBuddy( _this->m_pGPConnection, pBuddyStatusArg->profile ) != 0 );
				bool					moved = ( buddy.m_locationString != buddyStatus.locationString );

				if ( wasBuddy && ( moved || !isBuddy ) )
				{
					pFriendsOldSessionURL = buddy.m_locationString.c_str();
				}

				if ( isBuddy && ( moved || !wasBuddy ) )
				{
					pFriendsNewSessionURL = buddyStatus.locationString;
				}

				buddy.m_locationString = buddyStatus.locationString;
				buddy.m_statusString = buddyStatus.statusString;
				buddy.m_status = buddyStatus.status;
				buddy.m_lastUpdateTimestamp = pBuddyStatusArg->date;

				if ( isBuddy )
				{
					buddy.m_buddyBits |= eBB_IsBuddy;
				}
				else
				{
					buddy.m_buddyBits &= ~eBB_IsBuddy;
				}

				if (buddy.m_nickname.length() == 0 )
				{
					// Get the name for this profile
					gpGetInfo(_this->m_pGPConnection, pBuddyStatusArg->profile, GP_CHECK_CACHE, GP_NON_BLOCKING, GPGetInfoCallback, _this);
				}

				NetLog("[GameSpy] : status update for profile %i: status [%s] : location [%s]", pBuddyStatusArg->profile, buddy.m_statusString.c_str(), buddy.m_locationString.c_str());

				switch (buddyStatus.status)
				{
				case GP_ONLINE:
					TO_GAME_FROM_LOBBY(&CCryGameSpyFriends::DispatchProfileOnlineStatus, _this, pBuddyStatusArg->profile, eOS_SignedIn);
					break;

				case GP_OFFLINE:
					TO_GAME_FROM_LOBBY(&CCryGameSpyFriends::DispatchProfileOnlineStatus, _this, pBuddyStatusArg->profile, eOS_SignedOut);
					break;
				}

				if ( pFriendsOldSessionURL )
				{
					_this->GetService()->GetGameSpyMatchMaking()->AdjustSessionFriendCount( pFriendsOldSessionURL, -1 );
				}

				if ( pFriendsNewSessionURL )
				{
					_this->GetService()->GetGameSpyMatchMaking()->AdjustSessionFriendCount( pFriendsNewSessionURL, 1 );
				}
			}
			else
			{
				NetLog("[Lobby] : CCryGameSpyFriends::GPRecvBuddyStatusCallback() failed to retrieve buddy status for buddy index %i, error code %i", pBuddyStatusArg->index, result);
			}
		}
	}
}

void CCryGameSpyFriends::GPRecvBuddyMessageCallback(GPConnection* pGPConnection, void* pArg, void* pParam)
{
	CCryGameSpyFriends* _this = static_cast<CCryGameSpyFriends*>(pParam);
	if (pGPConnection == _this->m_pGPConnection)
	{
		GPRecvBuddyMessageArg* pBuddyMessageArg = static_cast<GPRecvBuddyMessageArg*>(pArg);

		// TODO: when we've decided what a suitable format for this message is, I'll add a
		// more robust test
		if (!memcmp(pBuddyMessageArg->message, "accepted", 8))
		{
			// This is an invite acceptance message - kick of NAT negotiation
			CCryGameSpyLobbyService* pLobbyService = static_cast<CCryGameSpyLobbyService*>(_this->m_pLobby->GetLobbyService(eCLS_Online));
			pLobbyService->InviteAcceptedAcknowledgement(_this->m_GPProfile ^ pBuddyMessageArg->profile);

			// No need to do any further processing here
			return;
		}

		SGameSpyProfile& buddy = _this->m_ProfileMap[pBuddyMessageArg->profile];
		if (buddy.m_lastUpdateTimestamp <= pBuddyMessageArg->date)
		{
			buddy.m_message = pBuddyMessageArg->message;
			buddy.m_lastUpdateTimestamp = pBuddyMessageArg->date;

			UCryLobbyEventData data;
			SCryLobbyFriendMessageData stateData;
			data.pFriendMesssageData = &stateData;

			stateData.m_user = new SCryGameSpyUserID(pBuddyMessageArg->profile);
			memset(stateData.m_message, 0, LOBBY_MESSAGE_SIZE);
			strncpy(stateData.m_message, pBuddyMessageArg->message, LOBBY_MESSAGE_SIZE - 1);

			if (stateData.m_user.IsValid())
			{
				_this->m_pLobby->DispatchEvent(eCLSE_FriendMessage, data);
			}

			_this->UpdateProfile(pGPConnection, pBuddyMessageArg->profile, pBuddyMessageArg->date);
		}
	}
}

void CCryGameSpyFriends::GPRecvBuddyUTMCallback(GPConnection* pGPConnection, void* pArg, void* pParam)
{
	CCryGameSpyFriends* _this = static_cast<CCryGameSpyFriends*>(pParam);
	if (pGPConnection == _this->m_pGPConnection)
	{
		GPRecvBuddyUTMArg* pBuddyUTMArg = static_cast<GPRecvBuddyUTMArg*>(pArg);

		SGameSpyProfile& buddy = _this->m_ProfileMap[pBuddyUTMArg->profile];
		if (buddy.m_lastUpdateTimestamp <= pBuddyUTMArg->date)
		{
			buddy.m_utmMessage = pBuddyUTMArg->message;
			buddy.m_lastUpdateTimestamp = pBuddyUTMArg->date;

			UCryLobbyEventData data;
			SCryLobbyFriendMessageData stateData;
			data.pFriendMesssageData = &stateData;

			stateData.m_user = new SCryGameSpyUserID(pBuddyUTMArg->profile);
			memset(stateData.m_message, 0, LOBBY_MESSAGE_SIZE);
			strncpy(stateData.m_message, pBuddyUTMArg->message, LOBBY_MESSAGE_SIZE - 1);

			if (stateData.m_user.IsValid())
			{
				_this->m_pLobby->DispatchEvent(eCLSE_FriendMessage, data);
			}

			_this->UpdateProfile(pGPConnection, pBuddyUTMArg->profile, pBuddyUTMArg->date);
		}
	}
}

void CCryGameSpyFriends::GPRecvGameInviteCallback(GPConnection* pGPConnection, void* pArg, void* pParam)
{
	CCryGameSpyFriends* _this = static_cast<CCryGameSpyFriends*>(pParam);
	if (pGPConnection == _this->m_pGPConnection)
	{
		GPRecvGameInviteArg* pGameInviteArg = static_cast<GPRecvGameInviteArg*>(pArg);

		SGameSpyProfile& buddy = _this->m_ProfileMap[pGameInviteArg->profile];
		buddy.m_invite = pGameInviteArg->location;

		UCryLobbyEventData data;
		SCryLobbyFriendMessageData stateData;
		data.pFriendMesssageData = &stateData;

		stateData.m_user = new SCryGameSpyUserID(pGameInviteArg->profile);
		memset(stateData.m_message, 0, LOBBY_MESSAGE_SIZE);
		strncpy(stateData.m_message, pGameInviteArg->location, LOBBY_MESSAGE_SIZE - 1);

		if (stateData.m_user.IsValid())
		{
			_this->m_pLobby->DispatchEvent(eCLSE_RecievedInvite, data);
		}

		NetLog("[Lobby] CCryGameSpyFriends::GPRecvGameInviteCallback() received invite from '%s' (%i), product id %i : %s", buddy.m_nickname.c_str(), pGameInviteArg->profile, pGameInviteArg->productID, pGameInviteArg->location);

		_this->UpdateProfile(pGPConnection, pGameInviteArg->profile, buddy.m_lastUpdateTimestamp);
	}
}

void CCryGameSpyFriends::GPRecvBuddyAuthCallback(GPConnection* pGPConnection, void* pArg, void* pParam)
{
	CCryGameSpyFriends* _this = static_cast<CCryGameSpyFriends*>(pParam);
	if (pGPConnection == _this->m_pGPConnection)
	{
		GPRecvBuddyAuthArg* pBuddyAuthArg = static_cast<GPRecvBuddyAuthArg*>(pArg);

		SGameSpyProfile& buddy = _this->m_ProfileMap[pBuddyAuthArg->profile];
		if (buddy.m_lastUpdateTimestamp <= pBuddyAuthArg->date)
		{
			if ( CNetCVars::Get().gamespySymmetricalFriends )
			{
				if ( ( buddy.m_buddyBits & ( eBB_SentForwardRequest | eBB_ReceivedReverseAccept ) ) == eBB_SentForwardRequest )
				{
					buddy.m_buddyBits |= eBB_ReceivedReverseAccept;
					NetLog("[GameSpy] : profile %i friend authorisation received", pBuddyAuthArg->profile);
					TO_GAME_FROM_LOBBY( &CCryGameSpyFriends::DispatchFriendIDEvent, _this, eCLSE_FriendAuthorised, pBuddyAuthArg->profile );
				}

				if ( ( buddy.m_buddyBits & eBB_SetupAnswerMask ) == eBB_ExpectingForwardAccept )
				{
					buddy.m_buddyBits |= eBB_ReceivedForwardAccept;
				}

				if ( !( buddy.m_buddyBits & eBB_InActiveQueue ) )
				{
					buddy.m_buddyBits |= eBB_InActiveQueue;
					_this->m_activeProfileQueue.push( pBuddyAuthArg->profile );
				}
			}
			else
			{
				NetLog("[GameSpy] : profile %i friend authorisation received", pBuddyAuthArg->profile);
				TO_GAME_FROM_LOBBY( &CCryGameSpyFriends::DispatchFriendIDEvent, _this, eCLSE_FriendAuthorised, pBuddyAuthArg->profile );
			}

			buddy.m_buddyBits |= eBB_IsBuddy;
			_this->UpdateProfile(pGPConnection, pBuddyAuthArg->profile, pBuddyAuthArg->date);
		}
	}
}

void CCryGameSpyFriends::GPRecvBuddyRevokeCallback(GPConnection* pGPConnection, void* pArg, void* pParam)
{
	CCryGameSpyFriends* _this = static_cast<CCryGameSpyFriends*>(pParam);
	if (pGPConnection == _this->m_pGPConnection)
	{
		GPRecvBuddyRevokeArg* pBuddyRevokeArg = static_cast<GPRecvBuddyRevokeArg*>(pArg);

		TProfileMapIterator it = _this->m_ProfileMap.find(pBuddyRevokeArg->profile);
		if (it != _this->m_ProfileMap.end())
		{
			if (it->second.m_lastUpdateTimestamp <= pBuddyRevokeArg->date)
			{
				if ( it->second.m_buddyBits & eBB_SentForwardRevoke )
				{
					it->second.m_buddyBits |= eBB_ReceivedReverseRevoke;
				}
				else
				{
					it->second.m_buddyBits |= eBB_ReceivedForwardRevoke;

					NetLog( "[GameSpy] : profile %i friend authorisation revoked", pBuddyRevokeArg->profile );
					TO_GAME_FROM_LOBBY( &CCryGameSpyFriends::DispatchFriendIDEvent, _this, eCLSE_FriendRevoked, pBuddyRevokeArg->profile );
				}

				if ( !( it->second.m_buddyBits & eBB_InActiveQueue ) )
				{
					it->second.m_buddyBits |= eBB_InActiveQueue;
					_this->m_activeProfileQueue.push( pBuddyRevokeArg->profile );
				}

				it->second.m_buddyBits &= ~eBB_IsBuddy;
			}
		}
		else
		{
			NetLog("[Lobby] CCryGameSpyFriends::GPRecvBuddyRevokeCallback() cannot revoke buddy %i - not a current buddy", pBuddyRevokeArg->profile);
		}
	}
}


void CCryGameSpyFriends::DispatchFriendIDEvent( ECryLobbySystemEvent event, GPProfile profile )
{
	UCryLobbyEventData		data;
	SCryLobbyFriendIDData stateData;

	data.pFriendIDData = &stateData;
	stateData.m_user = new SCryGameSpyUserID( profile );

	if ( stateData.m_user.IsValid() )
	{
		m_pLobby->DispatchEvent( event, data );
	}
}

void CCryGameSpyFriends::GPGetReverseBuddyListCallback( GPConnection* pGPConnection, void* pArg, void* pParam )
{
	CCryGameSpyFriends*							pThis = static_cast< CCryGameSpyFriends* >( pParam );

	if ( pGPConnection == pThis->m_pGPConnection )
	{
		GPGetReverseBuddiesResponseArg*	pGetReverseBuddiesResponseArg = static_cast< GPGetReverseBuddiesResponseArg* >( pArg );

		if ( pGetReverseBuddiesResponseArg->result == GP_NO_ERROR )
		{
			for ( uint32 i = 0; i < pGetReverseBuddiesResponseArg->numProfiles; ++i )
			{
				SGameSpyProfile&								buddy = pThis->m_ProfileMap[ pGetReverseBuddiesResponseArg->profiles[ i ].profile ];

				buddy.m_buddyBits |= eBB_IsReverseBuddy;
				buddy.m_nickname = pGetReverseBuddiesResponseArg->profiles[ i ].uniquenick;
			}			
		}
	}
}

void CCryGameSpyFriends::GPGetInfoCallback(GPConnection* pGPConnection, void* pArg, void* pParam)
{
	CCryGameSpyFriends* _this = static_cast<CCryGameSpyFriends*>(pParam);
	if (pGPConnection == _this->m_pGPConnection)
	{
		GPGetInfoResponseArg* pInfoResponseArg = static_cast<GPGetInfoResponseArg*>(pArg);

		TProfileMapIterator it = _this->m_ProfileMap.find(pInfoResponseArg->profile);
		if (it != _this->m_ProfileMap.end())
		{
			it->second.m_nickname = pInfoResponseArg->uniquenick;
		}

		// If this callback was part of a task and there are any more names that need filling in, start another callback
		CryFriendsTaskID fTaskID = _this->GetTaskID(pGPConnection, pInfoResponseArg->profile);
		if (fTaskID != CryFriendsInvalidTaskID)
		{
			STask* pTask = &(_this->m_task[fTaskID]);
			bool stillWorking = false;
			for (TProfileMapIterator it = _this->m_ProfileMap.begin(); it != _this->m_ProfileMap.end(); ++it)
			{
				if (it->second.m_nickname.length() == 0)
				{
					stillWorking = true;
					pTask->m_timeStarted = g_time;
					pTask->paramsNum[FRIENDS_PARAM_CURRENT_GPPROFILE] = it->first;
					_this->UpdateTaskError(fTaskID, CCryGameSpyLobbyService::GetErrorFromGameSpy(gpGetInfo(_this->m_pGPConnection, it->first, GP_CHECK_CACHE, GP_NON_BLOCKING, GPGetInfoCallback, _this), _this->m_pGPConnection));

					break;
				}
			}

			if (!stillWorking)
			{
				_this->StopTaskRunning(fTaskID);
			}
		}
	}
}

void CCryGameSpyFriends::OnGameSpyObjectAvailable( EGameSpyObjectBit object )
{
	if ( object & eGSOB_GPConnection )
	{
		for ( uint32 i = 0; i < MAX_FRIENDS_TASKS; ++i )
		{
			STask* pTask = &m_task[ i ];

			if ( pTask->used )
			{
				pTask->m_haveGPConnection = true;
			}
		}
	}
}

void CCryGameSpyFriends::OnGameSpyObjectUnavailable( EGameSpyObjectBit object )
{
	if ( object & eGSOB_GPConnection )
	{
		for ( uint32 i = 0; i < MAX_FRIENDS_TASKS; ++i )
		{
			STask* pTask = &m_task[ i ];

			if ( pTask->used )
			{
				pTask->m_haveGPConnection = false;

				switch ( pTask->startedTask )
				{
				case eT_FriendsGetFriendsList:
				case eT_FriendsSendGameInvite:
					if ( pTask->m_taskStarted )
					{
						UpdateTaskError( i, eCLE_UserNotSignedIn );
					}
					break;
				default:
					NetLogAlways( "[GameSpy] Task type %d not handled by CCryGameSpyFriends::OnGameSpyObjectUnavailable", pTask->startedTask );
					break;
				}
			}
		}
	}
}

void CCryGameSpyFriends::OnGameSpyObjectCantMakeAvailable( EGameSpyObjectBit object )
{
	if ( object & eGSOB_GPConnection )
	{
		for ( uint32 i = 0; i < MAX_FRIENDS_TASKS; ++i )
		{
			STask* pTask = &m_task[ i ];

			if ( pTask->used )
			{
				switch ( pTask->startedTask )
				{
				case eT_FriendsGetFriendsList:
				case eT_FriendsSendGameInvite:
					UpdateTaskError( i, eCLE_UserNotSignedIn );
					break;
				default:
					NetLogAlways( "[GameSpy] Task type %d not handled by CCryGameSpyFriends::OnGameSpyObjectCantMakeAvailable", pTask->startedTask );
					break;
				}
			}
		}
	}
}

ECryLobbyError CCryGameSpyFriends::FriendsSendFriendRequest( GPProfile profile, const char* pReason )
{
	ECryLobbyError		error = eCLE_Success;
	GPResult					result;
	SGameSpyProfile&	buddy = m_ProfileMap[ profile ];

	result = gpSendBuddyRequest( m_pGPConnection, profile, pReason );

	if ( result == GP_NO_ERROR )
	{
		buddy.m_buddyBits |= eBB_SentForwardRequest;
	}
	else
	{
		error = CCryGameSpyLobbyService::GetErrorFromGameSpy( result, m_pGPConnection );
	}

	if ( error != eCLE_Success )
	{
		buddy.m_buddyBits &= ~( eBB_SetupOriginateMask | eBB_SetupAnswerMask );
	}

	return error;
}

ECryLobbyError CCryGameSpyFriends::FriendsAcceptFriendRequest( GPProfile profile )
{
	ECryLobbyError		error = eCLE_Success;
	GPResult					result;
	SGameSpyProfile&	buddy = m_ProfileMap[ profile ];

	if ( CNetCVars::Get().gamespySymmetricalFriends )
	{
		result = gpSendBuddyRequest( m_pGPConnection, profile, "CryNetwork reverse friend request" );

		if ( result == GP_NO_ERROR )
		{
			buddy.m_buddyBits |= eBB_SentReverseRequest;
		}
		else
		{
			error = CCryGameSpyLobbyService::GetErrorFromGameSpy( result, m_pGPConnection );
		}
	}

	result = gpAuthBuddyRequest( m_pGPConnection, profile );

	if ( result == GP_NO_ERROR )
	{
		buddy.m_buddyBits |= ( eBB_SentReverseAccept | eBB_IsReverseBuddy );
	}
	else
	{
		error = CCryGameSpyLobbyService::GetErrorFromGameSpy( result, m_pGPConnection );
	}

	if ( error != eCLE_Success )
	{
		buddy.m_buddyBits &= ~( eBB_SetupOriginateMask | eBB_SetupAnswerMask );
	}

	return error;
}

ECryLobbyError CCryGameSpyFriends::FriendsDenyFriendRequest( GPProfile profile )
{
	ECryLobbyError		error = eCLE_Success;
	GPResult					result;
	SGameSpyProfile&	buddy = m_ProfileMap[ profile ];

	result = gpDenyBuddyRequest( m_pGPConnection, profile );

	if ( result == GP_NO_ERROR )
	{
		buddy.m_buddyBits &= ~eBB_ReceivedForwardRequest;
	}
	else
	{
		error = CCryGameSpyLobbyService::GetErrorFromGameSpy( result, m_pGPConnection );
	}

	if ( error != eCLE_Success )
	{
		buddy.m_buddyBits &= ~( eBB_SetupOriginateMask | eBB_SetupAnswerMask );
	}

	return error;
}

ECryLobbyError CCryGameSpyFriends::FriendsRevokeFriendStatus( GPProfile profile )
{
	ECryLobbyError		error = eCLE_Success;
	GPResult					result;
	SGameSpyProfile&	buddy = m_ProfileMap[ profile ];

	result = gpRevokeBuddyAuthorization( m_pGPConnection, profile );

	if ( result == GP_NO_ERROR )
	{
		buddy.m_buddyBits |= eBB_SentForwardRevoke;
		buddy.m_buddyBits &= ~eBB_IsReverseBuddy;
	}
	else
	{
		error = CCryGameSpyLobbyService::GetErrorFromGameSpy( result, m_pGPConnection );
	}

	result = gpDeleteBuddy( m_pGPConnection, profile );

	if ( result == GP_NO_ERROR )
	{
		buddy.m_buddyBits &= ~eBB_IsBuddy;
	}
	else
	{
		error = CCryGameSpyLobbyService::GetErrorFromGameSpy( result, m_pGPConnection );
	}

	if ( error != eCLE_Success )
	{
		buddy.m_buddyBits &= ~( eBB_RevokeOriginateMask | eBB_RevokeAnswerMask );
	}

	return error;
}

uint32 CCryGameSpyFriends::GetFriendCountInSession( const char* pUrl )
{
	uint32		result = 0;

	for ( TProfileMapIterator it = m_ProfileMap.begin(); it != m_ProfileMap.end(); ++it )
	{
		if ( ( it->second.m_buddyBits & eBB_IsBuddy ) && ( it->second.m_locationString == pUrl ) )
		{
			++result;
		}
	}

	return result;
}

ECryLobbyError CCryGameSpyFriends::GetFriendStatus( GPProfile profile, SFriendStatusInfo* pInfo )
{
	ECryLobbyError			error = eCLE_Success;

	pInfo->userID = new SCryGameSpyUserID( profile );

	if ( pInfo->userID.IsValid() )
	{
		TProfileMapIterator	it = m_ProfileMap.find( profile );

		if ( it != m_ProfileMap.end() )
		{
			switch ( it->second.m_status )
			{
			case GP_OFFLINE:
				pInfo->status = eFMS_Offline;
				break;
			case GP_ONLINE:
				pInfo->status = eFMS_Online;
				break;
			case GP_PLAYING:
				pInfo->status = eFMS_Playing;
				break;
			case GP_STAGING:
				pInfo->status = eFMS_Staging;
				break;
			case GP_CHATTING:
				pInfo->status = eFMS_Chatting;
				break;
			case GP_AWAY:
				pInfo->status = eFMS_Away;
				break;
			default:
				pInfo->status = eFMS_Unknown;
				break;
			}

			strcpy( pInfo->statusString, it->second.m_statusString.c_str() );
			strcpy( pInfo->locationString, it->second.m_locationString.c_str() );
		}
		else
		{
			pInfo->status = eFMS_Unknown;
			strcpy( pInfo->statusString, "Unknown" );
			pInfo->locationString[ 0 ] = '\0';
		}
	}
	else
	{
		error = eCLE_OutOfMemory;
	}

	return error;
}

#endif // USE_CRYLOBBY_GAMESPY
